﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_CAMERA_H
#define _UXS_CAMERA_H

#pragma once

#include "TTransform.h"
#include "TFrustum.h"

namespace UX_STL
{
namespace UXMathExt
{

	//
	// Perspective Camera
	//

	template <typename T>
	struct TCamera
	{
	protected:
		TTransform<T>		_sTransform;
		TMatrix<T,4,4>		_sProjMatrix;
		TMatrix<T,4,4>		_sViewMatrix;
		TMatrix<T,4,4>		_sViewProj;
		TFrustum<T>			_sFrustum;
		TVec<T,3>			_vRotation;
		TVec<T,3>			_vRotationMin,
							_vRotationMax;
		bvec3				_vRotationLimitMask;
		TVec<T,2>			_vClipPlanes;			// near, far
		T					_tFovY,					// rad
							_tAspect,				// width/height
							_tZoom;
		uint8				_uChanged		: 4;	// 1- view proj matrix, 2 - for frustum
		bool				_bLimitRotation : 1;
	
	protected:
		TVec<T,3>		&	_Position()					{ return _sTransform.Position(); }
		TQuaternion<T>	&	_Orientation()				{ return _sTransform.Orientation(); }
		TTransform<T>	&	_Transform()				{ return _sTransform; }
		TMatrix<T,4,4>	&	_ProjMatrix()				{ return _sProjMatrix; }
		TMatrix<T,4,4>	&	_ViewMatrix()				{ return _sViewMatrix; }

		void _Rotate(const TVec<T,3> &vRotationDelta);
		void _LimitRotation(TVec<T,3> &vRotationDelta) const;

	public:
		TCamera();
		
		TVec<T,3>	  const & Rotation()		const	{ return _vRotation; }
		TVec<T,3>	  const	& Position()		const	{ return _sTransform.Position(); }
		TQuaternion<T>const & Orientation()		const	{ return _sTransform.Orientation(); }
		TMatrix<T,4,4>const & ViewMatrix()		const	{ return _sViewMatrix; }
		TTransform<T> const & Transform()		const	{ return _sTransform; }
		TMatrix<T,4,4>const	& ProjMatrix()		const	{ return _sProjMatrix; }
		
		TFrustum<T>	  const & Frustum();
		TMatrix<T,4,4>const & ViewProjMatrix();

		TMatrix<T,4,4>const	GetModelMatrix()	const;

		TVec<T,3>	const	GetAxisX()			const	{ return TVec<T,3>( _sViewMatrix(0,0), _sViewMatrix(1,0), _sViewMatrix(2,0) ); }
		TVec<T,3>	const	GetAxisY()			const	{ return TVec<T,3>( _sViewMatrix(0,1), _sViewMatrix(1,1), _sViewMatrix(2,1) ); }
		TVec<T,3>	const	GetAxisZ()			const	{ return TVec<T,3>( _sViewMatrix(0,2), _sViewMatrix(1,2), _sViewMatrix(2,2) ); }

		TVec<T,2> const &	ClipPlanes()		const	{ return _vClipPlanes; }
		T const &			GetAspect()			const	{ return _tAspect; }
		//T const &			GetDefaultFovYrad()	const	{ return _tFovY; }
		T const				GetFovY()			const	{ return RTD( _tFovY * _tZoom ); }
		TVec<T,2> const		GetFOVs()			const	{ return RTD( TVec<T,2>(_tFovY / _tAspect, _tFovY) * _tZoom ); }
		T const &			GetZoom()			const	{ return _tZoom; }

		T					VisibilityRange()	const	{ return _vClipPlanes.y - _vClipPlanes.x; }

		void Create(const TTransform<T> &sTr, T fFovY, T fAspect, const TVec<T,2> &vClipPlanes);
		void Create(const TTransform<T> &sTr, const fvec2 &vSize, T fDistance, const TVec<T,2> &vClipPlanes);
	
		void SetClipPlanes(const TVec<T,2> &vClipPlanes);
		void Recalculate();

		void Resize(T fFovY, T fAspect);
		void Resize(const fvec2 &vSize, T fDistance);

		void Increase(T fZoom = 1.f);

		void LimitRotation(bool bLimit, const TVec<T,3> &vMin = TVec<T,3>(), const TVec<T,3> &vMax = TVec<T,3>());
		void LimitRotationRad(bool bLimit, const TVec<T,3> &vMin = TVec<T,3>(), const TVec<T,3> &vMax = TVec<T,3>());


		// FPS Camera //
		void RotateFPSRad(const TVec<T,2> &vRotationRad);
		void RotateFPS(const TVec<T,2> &vRotationDeg);
		void MoveFPS(const TVec<T,3> &vDelta);
		void TransformFPS(const TVec<T,3> &vDelta, TVec<T,3> &vPosition);

		// Free FPS Camera //
		void MoveFPSFree(const TVec<T,3> &vDelta);
		void TransformFPSFree(const TVec<T,3> &vDelta, TVec<T,3> &vPosition);

		// Flight Camera //
		void RotateFlightRad(const TVec<T,3> &vRotationRad);
		void RotateFlight(const TVec<T,3> &vRotationDeg);
		void MoveFlight(const TVec<T,3> &vDelta);
		void TransformFlight(const TVec<T,3> &vDelta, TVec<T,3> &vPosition);

		// Other //
		void MoveToPosition(const TVec<T,3> &vPos);


		static const TVec<T,3> UpDir()			{ return TVec<T,3>( T(0), T(1), T(0) ); }
		static const TVec<T,3> RightDir()		{ return TVec<T,3>( T(1), T(0), T(0) ); }
		static const TVec<T,3> ForwardDir()		{ return TVec<T,3>( T(0), T(0), T(1) ); }
	};

	


	template <typename T>
	inline TCamera<T>::TCamera():
		_sTransform(), _sProjMatrix(), _tFovY(0.5), _tAspect(1),
		_tZoom(1), _vClipPlanes(1, 100), _uChanged(3), _bLimitRotation(false)
	{
		_sProjMatrix = TMatrix<T,4,4>::Identity();
		_sViewMatrix = TMatrix<T,4,4>::Identity();
		_sViewProj	 = TMatrix<T,4,4>::Identity();
	}


	template <typename T>
	inline TMatrix<T,4,4> const TCamera<T>::GetModelMatrix() const
	{
		return TMatrix<T,4,4>::Translate( Position() );
	}


	template <typename T>
	inline TFrustum<T> const & TCamera<T>::Frustum()
	{
		if ( _uChanged & 0x2 )
		{
			_sFrustum.Setup( ViewProjMatrix() );
			_uChanged ^= 0x2;
		}

		return _sFrustum;
	}

	
	template <typename T>
	inline const TMatrix<T,4,4> & TCamera<T>::ViewProjMatrix()
	{
		if ( _uChanged & 0x1 )
		{
			_sViewProj = _sProjMatrix * _sViewMatrix;
			_uChanged ^= 0x1;
		}

		return _sViewProj;
	}

	
	template <typename T>
	inline void TCamera<T>::Create(const TTransform<T> &sTr, T fFovY, T fAspect, const TVec<T,2> &vClipPlanes)
	{
		_tZoom			= T(1);
		_sTransform		= sTr;
		_vClipPlanes	= vClipPlanes;
		_sViewMatrix	= TMatrix<T,4,4>::FromQuat( sTr.Orientation() );
		Resize( fFovY, fAspect );
	}

	
	template <typename T>
	inline void TCamera<T>::Create(const TTransform<T> &sTr, const fvec2 &vSize, T fDistance, const TVec<T,2> &vClipPlanes)
	{
		_tZoom			= T(1);
		_sTransform		= sTr;
		_vClipPlanes	= vClipPlanes;
		_sViewMatrix	= TMatrix<T,4,4>::FromQuat( sTr.Orientation() );
		Resize( vSize, fDistance );
	}
	
	
	template <typename T>
	inline void TCamera<T>::SetClipPlanes(const TVec<T,2> &vClipPlanes)
	{
		_uChanged	|= 0x3;
		_vClipPlanes = vClipPlanes;
		Recalculate();
	}
	
	
	template <typename T>
	inline void TCamera<T>::Recalculate()
	{
		_sProjMatrix = TMatrix<T,4,4>::BuildPerspective( Clamp( _tFovY / _tZoom, T(0.1), T(170) ), _tAspect, _vClipPlanes );
	}


	template <typename T>
	inline void TCamera<T>::Resize(T fFovY, T fAspect)
	{
		_tFovY		 = fFovY;
		_tAspect	 = fAspect;
		_uChanged   |= 0x3;
		Recalculate();
	}
	

	template <typename T>
	inline void TCamera<T>::Resize(const fvec2 &vSize, T fDistance)
	{
		_tFovY		 = 2 * ATan( vSize.y / (2 * fDistance) );
		_tAspect	 = vSize.x / vSize.y;
		_uChanged   |= 0x3;
		Recalculate();
	}

	
	template <typename T>
	inline void TCamera<T>::Increase(T fZoom)
	{
		if ( !Equals( fZoom, _tZoom ) )
		{
			_uChanged	|= 0x3;
			_tZoom		 = fZoom;
			Recalculate();
		}
	}
	

	template <typename T>
	inline void TCamera<T>::LimitRotation(bool bLimit, const TVec<T,3> &vMin, const TVec<T,3> &vMax)
	{
		_bLimitRotation		= bLimit;
		_vRotationMin		= DTR(vMin);
		_vRotationMax		= DTR(vMax);
		_vRotationLimitMask	= vMin == vMax;
	}
	

	template <typename T>
	inline void TCamera<T>::LimitRotationRad(bool bLimit, const TVec<T,3> &vMin, const TVec<T,3> &vMax)
	{
		_bLimitRotation		= bLimit;
		_vRotationMin		= vMin;
		_vRotationMax		= vMax;
		_vRotationLimitMask	= vMin == vMax;
	}
	

	template <typename T>
	inline void TCamera<T>::_LimitRotation(TVec<T,3> &vRotationDelta) const
	{
		if ( _bLimitRotation )
		{
			TVec<T,3>	v_last_rot = _vRotation + vRotationDelta;
			//vRotationDelta = Clamp( v_last_rot, _vRotationMin, _vRotationMax ) - _vRotation;
			_vRotationLimitMask.x ? 0.0f : vRotationDelta.x = Clamp( v_last_rot.x, _vRotationMin.x, _vRotationMax.x ) - _vRotation.x;
			_vRotationLimitMask.y ? 0.0f : vRotationDelta.y = Clamp( v_last_rot.y, _vRotationMin.y, _vRotationMax.y ) - _vRotation.y;
			_vRotationLimitMask.z ? 0.0f : vRotationDelta.z = Clamp( v_last_rot.z, _vRotationMin.z, _vRotationMax.z ) - _vRotation.z;
		}
	}


	template <typename T>
	inline void TCamera<T>::_Rotate(const TVec<T,3> &vRotationDelta)
	{
		_vRotation += vRotationDelta;

		if ( Abs( _vRotation.x ) > Pi<T>() )	_vRotation.x -= CopySign( _vRotation.x, Pi<T>() );
		if ( Abs( _vRotation.y ) > Pi<T>() )	_vRotation.y -= CopySign( _vRotation.y, Pi<T>() );
		if ( Abs( _vRotation.z ) > Pi<T>() )	_vRotation.z -= CopySign( _vRotation.z, Pi<T>() );
	}

		
	template <typename T>
	inline void TCamera<T>::RotateFPSRad(const TVec<T,2> &vRotationRad)
	{
		// x - horizontal
		// y - vertical
		
		if ( IsZero( vRotationRad ) )
			return;

		TQuaternion<T> & q			= _Orientation();
		TVec<T,3>		 rot		= TVec<T,3>( vRotationRad, T(0.0) );
		bool			 b_changed	= false;

		_LimitRotation( rot );

		if ( IsNotZero( rot.y ) ) {
			q = q * TQuaternion<T>().RotationX( rot.y );
			b_changed = true;
		}
		
		if ( IsNotZero( rot.x ) ) {
			q = TQuaternion<T>().RotationY( rot.x ) * q;
			b_changed = true;
		}

		if ( b_changed )
		{
			_Rotate( rot );
			_uChanged |= 0x3;

			q.Normalize();
			_sViewMatrix = TMatrix<T,4,4>::FromQuat( q );
		}
	}

	
	template <typename T>
	inline void TCamera<T>::RotateFPS(const TVec<T,2> &vRotationDeg)
	{
		RotateFPSRad( DTR( vRotationDeg ) );
	}

	
	template <typename T>
	inline void TCamera<T>::TransformFPS(const TVec<T,3> &vDelta, TVec<T,3> &vPosition)
	{
		// x - forward/backward
		// y - side
		// z - up/down

		TVec<T,3> const	forwards = Cross( UpDir(), GetAxisX() ).Normalized();
		
		vPosition += forwards   * -vDelta.x;
		vPosition += GetAxisX() * -vDelta.y;
		vPosition += UpDir()    * -vDelta.z;
	}


	template <typename T>
	inline void TCamera<T>::MoveFPS(const TVec<T,3> &vDelta)
	{
		TransformFPS( vDelta, _Position() );
	}


	template <typename T>
	inline void TCamera<T>::TransformFPSFree(const TVec<T,3> &vDelta, TVec<T,3> &vPosition)
	{
		// x - forward/backward
		// y - side
		// z - up/down
		
		vPosition += GetAxisZ() *  vDelta.x;
		vPosition += GetAxisX() * -vDelta.y;
		vPosition += UpDir()    * -vDelta.z;
	}

		
	template <typename T>
	inline void TCamera<T>::MoveFPSFree(const TVec<T,3> &vDelta)
	{
		TransformFPSFree( vDelta, _Position() );
	}
	

	template <typename T>
	inline void TCamera<T>::RotateFlightRad(const TVec<T,3> &vRotationRad)
	{
		// x - roll
		// y - pitch
		// z - yaw

		if ( IsZero( vRotationRad ) )
			return;
		
		TVec<T,3>	rot = vRotationRad;
		_LimitRotation( rot );

		TQuaternion<T> & q = _Orientation();
		q = TQuaternion<T>( rot.y, rot.x, rot.z ) * q;
	
		_Rotate( rot );
		_uChanged |= 0x3;

		q.Normalize();
		_sViewMatrix = TMatrix<T,4,4>::FromQuat( q );
	}

	
	template <typename T>
	inline void TCamera<T>::RotateFlight(const TVec<T,3> &vRotationDeg)
	{
		RotateFlightRad( DTR( vRotationDeg ) );
	}


	template <typename T>
	inline void TCamera<T>::TransformFlight(const TVec<T,3> &vDelta, TVec<T,3> &vPosition)
	{
		// x - forward/backward
		// y - side
		// z - up/down

		vPosition += GetAxisX() * -vDelta.y;
		vPosition += UpDir()    *  vDelta.z;
		vPosition += GetAxisZ() *  vDelta.x;
	}

	
	template <typename T>
	inline void TCamera<T>::MoveFlight(const TVec<T,3> &vDelta)
	{
		TransformFlight( vDelta, _Position() );
	}


	template <typename T>
	inline void TCamera<T>::MoveToPosition(const TVec<T,3> &vPos)
	{
		_Position() = vPos;
	}

//-------------------------------------------------------------------

}	// UXMathExt
}	// UX_STL

#endif	// _UXS_CAMERA_H